﻿/*================================================================================================================================

  This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.  

  THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, 
  INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.  

  We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object 
  code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software 
  product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the 
  Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims 
  or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

 =================================================================================================================================*/

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Linq;


namespace Microsoft.Pfe.Xrm
{
    public class QuickFindOnSelectedView : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Extract the tracing service for use in debugging sandboxed plug-ins.
            // If you are not registering the plug-in in the sandbox, then you do
            // not have to add any tracing service related code.
            ITracingService tracingService =
                (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            // Obtain the execution context from the service provider.
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));

            // Obtain the organization service reference which you will need for
            // web service calls.
            IOrganizationServiceFactory serviceFactory =
                (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // If inputparameters does not contain Query then do nothing.
            if (!context.InputParameters.Contains("Query"))
                return;

            QueryExpression query = null;
            bool isFetch = false;

            // Check Query type. Only handles QueryExpression and FetchExpression
            if (context.InputParameters["Query"].GetType().Equals(typeof(QueryExpression)))
            {
                query = (QueryExpression)context.InputParameters["Query"];
                // If this is not grid view request, do nothing. (based on paging info, which is between 25-250)
                if (query.PageInfo.Count < 25 || query.PageInfo.Count > 250)
                    return;            
            }
            else if (context.InputParameters["Query"].GetType().Equals(typeof(FetchExpression)))
            {
                // Firstly check "count" attribute for "fetch" element.
                string fetchXml = (context.InputParameters["Query"] as FetchExpression).Query;
                XDocument xdoc = XDocument.Parse(fetchXml);
                string count = (xdoc.Element("fetch").Attribute("count") == null) ? "" : xdoc.Element("fetch").Attribute("count").Value;

                // If this is not grid view request, do nothing. (based on paging info, which is between 25-250)
                if (String.IsNullOrEmpty(count) || int.Parse(count) < 50 || int.Parse(count) > 250)
                    return;

                try
                {
                    // If fetch, then convert to QueryExpression
                    FetchXmlToQueryExpressionRequest request = new FetchXmlToQueryExpressionRequest()
                    {
                        FetchXml = fetchXml
                    };
                    FetchXmlToQueryExpressionResponse response = (FetchXmlToQueryExpressionResponse)service.Execute(request);
                    query = response.Query;
                    isFetch = true;
                }
                catch (Exception ex)
                {
                    // If failed to convert to QueryExpression, then log error and do nothing.
                    tracingService.Trace(String.Format("failed to convert FetchXML to QueryExpression : {0}", fetchXml));
                    return;
                }
            }
            else
                return;
            
            // If this is quick find (by checking IsQuickFindFilter property)
            if (query.Criteria.Filters.Where(x => x.IsQuickFindFilter == true).FirstOrDefault() != null)
            {
                // Retrived last used fetchxml
                string fetch = String.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
  <entity name='pfe_queryhistory'>
    <attribute name='pfe_fetchxml' />
    <filter type='and'>
      <condition attribute='pfe_entityname' operator='eq' value='{0}' />
      <condition attribute='createdby' operator='eq-userid' />
    </filter>
  </entity>
</fetch>", context.PrimaryEntityName);
                var queryhistory = service.RetrieveMultiple(new FetchExpression(fetch)).Entities.FirstOrDefault();

                // If no history data (with system invalid state), do notihng and use defalut QuickFind View query.
                if (queryhistory == null || !queryhistory.Contains("pfe_fetchxml") || queryhistory["pfe_fetchxml"] == null)
                    return;

                // Convert last used FetchXml to QueryExpression
                QueryExpression viewQuery;
                try
                {                    
                    FetchXmlToQueryExpressionRequest fetchXmlToQueryExpressionRequest = new FetchXmlToQueryExpressionRequest()
                    {
                        FetchXml = queryhistory["pfe_fetchxml"].ToString()
                    };
                    FetchXmlToQueryExpressionResponse fetchXmlToQueryExpressionResponse = (FetchXmlToQueryExpressionResponse)service.Execute(fetchXmlToQueryExpressionRequest);

                    viewQuery = fetchXmlToQueryExpressionResponse.Query;
                }
                catch(Exception ex)
                {
                    // If failed to convert to QueryExpression, then log error and do nothing.
                    tracingService.Trace(String.Format("failed to convert FetchXML to QueryExpression : {0}", (context.InputParameters["Query"] as FetchExpression).Query));
                    return;
                }

                // If there is a history, then firstly remove non QuickFind filter
                if (query.Criteria.Filters.Where(x => x.IsQuickFindFilter == false).FirstOrDefault() != null)
                    query.Criteria.Filters.Remove(query.Criteria.Filters.Where(x => x.IsQuickFindFilter == false).First());            

                // If this is FetchXml, request may come from Outlook and userentityinstancedata could duplicate, thus remove from viewQuery.
                if (isFetch && viewQuery.LinkEntities.Where(x => x.EntityAlias == "userentityinstancedata").FirstOrDefault() != null)
                    viewQuery.LinkEntities.Remove(viewQuery.LinkEntities.Where(x => x.EntityAlias == "userentityinstancedata").First());
                    
                // Add Criteria and LinkEntities information to QuickFind query
                query.Criteria.Conditions.AddRange(viewQuery.Criteria.Conditions);
                query.Criteria.Filters.AddRange(viewQuery.Criteria.Filters);
                query.LinkEntities.AddRange(viewQuery.LinkEntities);

                // If this is FetchExpression, then convert back to FetchExpression
                if(isFetch)
                {
                    try
                    {
                        QueryExpressionToFetchXmlRequest queryExpressionToFetchXmlRequest = new QueryExpressionToFetchXmlRequest() { Query = query };
                        QueryExpressionToFetchXmlResponse queryExpressionToFetchXmlResponse = (QueryExpressionToFetchXmlResponse)service.Execute(queryExpressionToFetchXmlRequest);
                        context.InputParameters["Query"] = new FetchExpression(queryExpressionToFetchXmlResponse.FetchXml);
                    }
                    catch (Exception ex)
                    {
                        // When error happens, log error with QueryExpression. As QueryExpression is a class, serialize it to String.
                        DataContractSerializer serializer = new DataContractSerializer(typeof(QueryExpression));
                        string serializedQuery = "";
                        using (MemoryStream stream = new MemoryStream())
                        {
                            serializer.WriteObject(stream, query);
                            serializedQuery = Encoding.ASCII.GetString(stream.ToArray());
                        }
                        // If failed to convert to FetchXml, then log error and do nothing.
                        tracingService.Trace(String.Format("failed to convert QueryExpression to FetchXml : {0}", serializedQuery));
                        return;
                    }
                }
                else
                    context.InputParameters["Query"] = query;
            }
            else
            {
                // If this is not 1st page in Grid, then query should be same, therefore do nothing.
                if (!String.IsNullOrEmpty(query.PageInfo.PagingCookie))
                    return;

                // Convert QueryExpression To FetchXml to store it. I don't use serialization as it fails under sandbox environment.
                // So simply store quiry as FetchXml.
                QueryExpressionToFetchXmlRequest queryExpressionToFetchXmlRequest = new QueryExpressionToFetchXmlRequest() { Query = query };
                QueryExpressionToFetchXmlResponse queryExpressionToFetchXmlResponse = (QueryExpressionToFetchXmlResponse)service.Execute(queryExpressionToFetchXmlRequest);
                
                // Retrieve last used query.
                string fetch = String.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
  <entity name='pfe_queryhistory'>
    <attribute name='pfe_queryhistoryid' />
    <filter type='and'>
      <condition attribute='pfe_entityname' operator='eq' value='{0}' />
      <condition attribute='createdby' operator='eq-userid' />
    </filter>
  </entity>
</fetch>",context.PrimaryEntityName);
                var queryhistory = service.RetrieveMultiple(new FetchExpression(fetch)).Entities.FirstOrDefault();

                // If this is no result back, then create one.
                if (queryhistory == null)
                {
                    Entity newhistory = new Entity("pfe_queryhistory");
                    newhistory["pfe_entityname"] = context.PrimaryEntityName;
                    newhistory["pfe_fetchxml"] = queryExpressionToFetchXmlResponse.FetchXml;
                    try
                    {
                        service.Create(newhistory);
                    }
                    catch(Exception ex)
                    {
                        tracingService.Trace(String.Format("failed to create Query History : {0}", ex.ToString()));
                        return;
                    }
                }
                else
                {
                    queryhistory["pfe_fetchxml"] = queryExpressionToFetchXmlResponse.FetchXml;
                    try
                    {
                        service.Update(queryhistory);
                    }
                    catch (Exception ex)
                    {
                        tracingService.Trace(String.Format("failed to update Query History : {0}", ex.ToString()));
                        return;
                    }
                }
            }
        }
    }
}
